/* @flow */

import Dep from './dep';
import VNode from '../vdom/vnode';
import { arrayMethods } from './array';
import {
    def,
    warn,
    hasOwn,
    hasProto,
    isObject,
    isPlainObject,
    isPrimitive,
    isUndef,
    isValidArrayIndex,
    isServerRendering
} from '../util/index';

const arrayKeys = Object.getOwnPropertyNames(arrayMethods);

/**
 * In some cases we may want to disable observation inside a component's
 * update computation.
 */
export let shouldObserve: boolean = true;

export function toggleObserving(value: boolean) {
    shouldObserve = value;
}

/**
 * Observer class that is attached to each observed
 * object. Once attached, the observer converts the target
 * object's property keys into getter/setters that
 * collect dependencies and dispatch updates.
 * 
 * 可观测类，数据通过Observer的转换成为可观测数据。
 * 
 * Observer 类附加到每一个被观察的对象上，为每一个被观察的对象添加一个Observer（观察者）
 * 一旦附加，这个observer会将模板对象的所有属性转换为 getter、setter。 
 * getter、setter用来收集依赖和派发更新（发送通知）。
 */
export class Observer {
    // 观测对象
    value: any;
    // 依赖对象
    dep: Dep;
    // 实例计数器
    vmCount: number; // number of vms that have this object as root $data

    this.value = value;

    constructor(value: any) {
        // 创建一个可以能够被观察的对象。每个Observer都要一个Dep
        this.dep = new Dep();

        // 初始化实例的 vmCount 为0
        this.vmCount = 0;
        
        // 给当前值添加 __ob__属性。将实例挂载到观察对象的 __ob__ 属性。def是Object.definePrototype的封装。
        // def将__ob__设置为不可枚举。 因为后续需要为可枚举的所有属性，添加getter、setter
        def(value, '__ob__', this);
        // 数组的响应式处理（）
        if (Array.isArray(value)) {
            // 判断当前浏览器是否支持__proto__原型属性
            if (hasProto) {
              // 通过使用 value.__proto__ 截取原型链增加原型链上目标对象或数组。
              // arrayMethods为数组原型。Array.prototype
              // 修改当前数组的原型，原型中修改了数组原有的push等方法
                protoAugment(value, arrayMethods);
            } else {
              // 将array.prototype上所有key方法拷贝至value上
                copyAugment(value, arrayMethods, arrayKeys);
            }

            // 为数组中的每一个对象创建一个 observer 实例
            // 遍历数组每一项，执行observer方法，为数组每一个成员添加一个observer观察者
            this.observeArray(value);
        } else {
            // 遍历对象中的每一个属性，转换成 setter/getter
            this.walk(value);
        }
    }

    /**
     * Walk through all properties and convert them into
     * getter/setters. This method should only be called when
     * value type is Object.
     * 
     * 遍历所有属性并将它们转换为getter/setter。
     * 这个方法应该只在值类型为Object时被调用
     */
    walk(obj: Object) {
        // 获取观察对象的每一个属性
        const keys = Object.keys(obj);
        // 遍历每一个属性，设置为响应式数据
        for (let i = 0; i < keys.length; i++) {
          // 给对象中属性添加getter、setter、收集依赖、发送通知
            defineReactive(obj, keys[i]);
        }
    }

    /**
     * Observe a list of Array items.
     */
    observeArray(items: Array<any>) {
        for (let i = 0, l = items.length; i < l; i++) {
            observe(items[i]);
        }
    }
}

// helpers

/**
 * Augment a target Object or Array by intercepting
 * the prototype chain using __proto__
 * 
 * 通过使用__proto__截取原型链来增加目标对象或数组
 * 重新设置当前对象的原型属性。
 */
function protoAugment(target, src: Object) {
    /* eslint-disable no-proto */
    target.__proto__ = src;
    /* eslint-enable no-proto */
}

/**
 * Augment a target Object or Array by defining
 * hidden properties.
 * 通过定义隐藏属性来增加目标对象或数组
 */
/* istanbul ignore next */
function copyAugment(target: Object, src: Object, keys: Array<string>) {
    for (let i = 0, l = keys.length; i < l; i++) {
        const key = keys[i];
        def(target, key, src[key]);
    }
}

/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 * 尝试为一个值创建一个observer实例(创建一个观察者)
 * 如果成功观察到，返回新的observer
 * 如果值已经有__ob__了，就是要现有的观察者
 *
 * 负责为每一个 Object类型的value创建一个observer实例
 */
export function observe(value: any, asRootData: ?boolean): Observer | void {
    // 如果值不是对象/虚拟DOM，直接返回不做处理。判断值是否为对象、是否为虚拟dom。
    if (!isObject(value) || value instanceof VNode) {
        return;
    }

    let ob: Observer | void;
    // value 的自身是否有 __ob__属性，并且 __ob__属性是 Observer的实例
    if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
        ob = value.__ob__;
    } else if (
        // 可被观察 & 在浏览器环境或微信环境或global不为undefined & value为数组/原生对象 & value可以扩展 & value没有 _isVUe属性
        // _isVue标记是否为vue实例
        shouldObserve &&
        !isServerRendering() &&
        (Array.isArray(value) || isPlainObject(value)) &&
        Object.isExtensible(value) &&
        !value._isVue
    ) {
        // 创建一个Observer对象，为value做响应式处理
        ob = new Observer(value);
    }
    //
    if (asRootData && ob) {
        ob.vmCount++;
    }
    return ob;
}

/**
 * Define a reactive property on an Object.
 * 将对象上属性转换为响应式
 * 
 * 参数4：customSetter，用户传入的自定义函数
 * 参数5：shallow?: boolean ，true，浅，false，深，处理对象内对象
 */
export function defineReactive(
    obj: Object,
    key: string,
    val: any,
    customSetter?: ?Function,
    shallow?: boolean
) {
    // 创建依赖对象实例。
    // 负责为当前属性收集依赖，收集当前观察属性的所有watcher
    const dep = new Dep();

    // 获取obj key的属性描述符对象
    const property = Object.getOwnPropertyDescriptor(obj, key);

    // 如果描述符configurable=false，不可配置，则直接返回
    if (property && property.configurable === false) {
        return;
    }

    // cater for pre-defined getter/setters
    // 提供预定义的存取器函数。
    // 对象可能是用户定义的，首先获取到用户定义的对象的get、set，重写get和set，增加收集依赖和派发更新的功能。
    const getter = property && property.get;
    const setter = property && property.set;
    // 没有getter、只有setter
    if ((!getter || setter) && arguments.length === 2) {
        val = obj[key];
    }

    // 判断是否递归观察子对象，并将子对象属性都转换成 getter/setter，返回子观察对象
    // 如果shallow是false，则会去深度监听对象，如果当前的val是对象，会将val对象属性转换为getter和setter
    let childOb = !shallow && observe(val);
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,

        
        get: function reactiveGetter() {
            // 如果预定义的 getter 存在,则 value 等于getter 调用的返回值
            // 否则直接赋予属性值
            const value = getter ? getter.call(obj) : val;
            // 在get方法中完成依赖收集，当返回值时进行依赖收集。将依赖该值的watcher加入到subs数组中，当值变化时通知所有watcher。
            // 如果存在当前依赖目标，即 watcher 对象，则建立依赖
            // Dep.target存储当前值的所有watcher对象，是否有target
            if (Dep.target) {
                // depend进行依赖收集，将依赖该值的watcher添加到subs数组中
                dep.depend();
                // 如果子观察目标存在，建立子对象的依赖关系
                // childOb 子对象的observer
                if (childOb) {
                    // Observer中的dep为子对象收集依赖
                    // defineReactive中为每个属性添加dep，为每个属性收集依赖
                    childOb.dep.depend();
                    
                    // 如果属性是数组，则特殊处理收集数组对象依赖
                    if (Array.isArray(value)) {
                        dependArray(value);
                    }
                }
            }
            return value;
        },
        set: function reactiveSetter(newVal) {
            // 如果预定义的 getter 存在则 value 等于getter 调用的返回值
            // 否则直接赋予属性值
            const value = getter ? getter.call(obj) : val;

            // 如果新值等于旧值或者新值旧值为NaN则不执行
            /* eslint-disable no-self-compare */
            if (newVal === value || (newVal !== newVal && value !== value)) {
                return;
            }
            /* eslint-enable no-self-compare */
            if (process.env.NODE_ENV !== 'production' && customSetter) {
                customSetter();
            }
            // #7981: for accessor properties without setter
            // 如果没有 setter 直接返回。当前属性为只读，返回，不处理。
            if (getter && !setter) return;
            // 如果预定义setter存在则调用，否则直接更新新值
            if (setter) {
                setter.call(obj, newVal);
            } else {
                val = newVal;
            }

            // 如果新值是对象，观察子对象并返回 子的 observer 对象
            childOb = !shallow && observe(newVal);
            // 派发更新(发布更改通知)
            dep.notify();
        }
    });
}

/**
 * Set a property on an object. Adds the new property and
 * triggers change notification if the property doesn't
 * already exist.
 */
export function set(target: Array<any> | Object, key: any, val: any): any {
    // 源对象是否为undefined或是否为原始值，不能给原始值和undefined进行响应式设置
    if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target))) {
        warn(
            `Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`
        );
    }
     // 判断是否是数组，以及 key 是否为合法索引
    if (Array.isArray(target) && isValidArrayIndex(key)) {
        target.length = Math.max(target.length, key);
        // 通过splice对key位置的元索进行替换
        // splice在array.js 进行了响应化的处理。此处的splice方法时vue增强后的方法，可触发setter，dep发送notify   
        target.splice(key, 1, val);
        return val;
    }

    // 如果key在对象中已经存在直接赋值
    if (key in target && !(key in Object.prototype)) {
        target[key] = val;
        return val;
    }

    // 获取target 中的observer 对象
    const ob = (target: any).__ob__;

    // 如果target是vue 实例或者$data 直接返回。如果是 $date ,vmCount值为1，其余属性值为0
    if (target._isVue || (ob && ob.vmCount)) {
        process.env.NODE_ENV !== 'production' &&
            warn(
                'Avoid adding reactive properties to a Vue instance or its root $data ' +
                    'at runtime - declare it upfront in the data option.'
            );
        return val;
    }

    // 如果ob不存在，Target不是响应式对象直接赋值
    if (!ob) {
        target[key] = val;
        return val;
    }
     //把key设置为响应式属性
    defineReactive(ob.value, key, val);
    // 发送通知
    ob.dep.notify();
    return val;
}

/**
 * Delete a property and trigger change if necessary.
 */
export function del(target: Array<any> | Object, key: any) {
    if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target))) {
        warn(
            `Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`
        );
    }
    if (Array.isArray(target) && isValidArrayIndex(key)) {
        target.splice(key, 1);
        return;
    }
    const ob = (target: any).__ob__;
    if (target._isVue || (ob && ob.vmCount)) {
        process.env.NODE_ENV !== 'production' &&
            warn(
                'Avoid deleting properties on a Vue instance or its root $data ' +
                    '- just set it to null.'
            );
        return;
    }
    if (!hasOwn(target, key)) {
        return;
    }
    delete target[key];
    if (!ob) {
        return;
    }
    ob.dep.notify();
}

/**
 * Collect dependencies on array elements when the array is touched, since
 * we cannot intercept array element access like property getters.
 * 当数组被触摸时，收集数组元素的依赖项
 * 因为我们不能拦截数组元素访问，就像属性getter
 */
function dependArray(value: Array<any>) {
    for (let e, i = 0, l = value.length; i < l; i++) {
        e = value[i];
        e && e.__ob__ && e.__ob__.dep.depend();
        if (Array.isArray(e)) {
            dependArray(e);
        }
    }
}
